﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sinacor.Infra.Service.Data;
using System.Data.Common;
using System.Data;
using Microsoft.Win32;
using Sinacor.Infra.Common.Exceptions;

namespace Sinacor.Infra.Service.Security.Session
{
    /// <summary>
    /// Efetua o controle da sessão
    /// </summary>
    public class SessionControl
    {
        /// <summary>
        /// Efetua a criação de uma nova sessão
        /// </summary>
        public SessionData Create(int userId, int companyId, string hostName)
        {

            DateTime expiredDate = RetrieveExpiredDate();
            DateTime updateDate = DateTime.Now;

            string query = @"INSERT INTO TFWSESS (CD_USUARIO,COD_EMP,DATA_EXP,NOME_HOST_USUA,DATA_ATLZ) VALUES(:UserId,:CompanyId,:ExpiredDate,:HostName,:UpdateDate)";

            SessionData sessionData = new SessionData();

            using (OracleDataBase db = new OracleDataBase())
            {
                using (DbCommand cmd = db.GetSqlStringCommand(query))
                {
                    db.AddInParameter(cmd, "UserId", DbType.Int32, userId);
                    db.AddInParameter(cmd, "CompanyId", DbType.Int32, companyId);
                    db.AddInParameter(cmd, "ExpiredDate", DbType.Date, expiredDate);
                    db.AddInParameter(cmd, "HostName", DbType.String, hostName);
                    db.AddInParameter(cmd, "UpdateDate", DbType.Date, updateDate);
                    db.ExecuteNonQuery(cmd);
                }

                //Efetua o select para retornar o código da sessão
                query = @"SELECT COD_SESS FROM TFWSESS WHERE CD_USUARIO=:UserID AND COD_EMP=:CompanyId AND NOME_HOST_USUA = :HostName AND DATA_ATLZ = :UpdateDate";

                using (DbCommand cmd = db.GetSqlStringCommand(query))
                {
                    db.AddInParameter(cmd, "UserId", DbType.Int32, userId);
                    db.AddInParameter(cmd, "CompanyId", DbType.Int32, companyId);
                    db.AddInParameter(cmd, "HostName", DbType.String, hostName);
                    db.AddInParameter(cmd, "UpdateDate", DbType.Date, updateDate);

                    using (IDataReader reader = db.ExecuteReader(cmd))
                    {
                        if (reader.Read())
                        {
                            sessionData.Id = reader.GetValue<Int32>("COD_SESS");
                            sessionData.UserId = userId;
                            sessionData.CompanyId = companyId;
                            sessionData.ExpiredDate = expiredDate;
                            sessionData.HostName = hostName;
                            sessionData.UpdateDate = updateDate;
                        }
                    }
                }
            }
            return sessionData;
        }

        /// <summary>
        /// Retorna a Data de Expiração da Sessão.
        /// </summary>
        /// <returns></returns>
        private DateTime RetrieveExpiredDate()
        {
            RegistryKey key = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Sinacor\Session");
            string timeOut = (string)key.GetValue("TimeOut");
            if (!String.IsNullOrEmpty(timeOut))
                return DateTime.Now.AddMinutes(int.Parse(timeOut));
            else
                return DateTime.Now.AddMinutes(15);
        }

        /// <summary>
        /// Atualiza uma sessão existente
        /// </summary>
        private bool Update(Int32 sessionId, DateTime expiredDate)
        {
            string query = @"UPDATE TFWSESS SET DATA_EXP = :ExpiredDate, DATA_ATLZ = :UpdateDate WHERE COD_SESS = :SessionId";

            using (OracleDataBase db = new OracleDataBase())
            {
                try
                {
                    using (DbCommand cmd = db.GetSqlStringCommand(query))
                    {
                        db.AddInParameter(cmd, "SessionId", DbType.Int32, sessionId);
                        db.AddInParameter(cmd, "ExpiredDate", DbType.Date, expiredDate);
                        db.AddInParameter(cmd, "UpdateDate", DbType.Date, DateTime.Now);
                        db.ExecuteNonQuery(cmd);
                    }
                }
                catch (Exception ex)
                {
                    //Caso a atualização ocorra no momento em que não existir mais a sessão,
                    //retorna false;
                    //TODO: ADICIONAR TRATAMENTO DE LOGGING.
                    //System.Diagnostics.EventLog.WriteEntry("SessionControl.Update", ex.Message);
                    throw new InvalidSessionException("Error trying to update session", ex);
                }
            }
            return true;
        }

        /// <summary>
        /// Remove uma sessão
        /// </summary>
        public void Remove(Int32 sessionId)
        {
            using (OracleDataBase db = new OracleDataBase())
            {
                string query = @"DELETE FROM TFWSESS WHERE COD_SESS = :SessionId";

                SessionData sessionData = new SessionData();

                using (DbCommand cmd = db.GetSqlStringCommand(query))
                {
                    db.AddInParameter(cmd, "SessionId", DbType.Int32, sessionId);
                    db.ExecuteNonQuery(cmd);
                }
            }
        }

        /// <summary>
        /// Remove todas as sessões do usuário.
        /// </summary>
        public void RemoveByUserId(Int32 userId)
        {
            RemoveSession(userId, 0);
        }

        /// <summary>
        /// Efetua a validação de uma sessão
        /// </summary>
        public bool Validate(Int32 sessionId)
        {
            SessionData sessionData = Retrieve(sessionId);
            //Se a sessão não existir, retorna false
            if (null == sessionData)
                return false;

            //Se a data de expiração for menor que a data atual, retorna false;
            if (DateTime.Now.CompareTo(sessionData.ExpiredDate) > 0)
                return false;

            return true;
        }

        /// <summary>
        /// Retorna os valores de uma sessão
        /// </summary>
        /// <param name="sessionId"></param>
        public SessionData Retrieve(Int32 sessionId)
        {

            string query = @"SELECT COD_SESS,CD_USUARIO,COD_EMP,DATA_EXP,NOME_HOST_USUA FROM TFWSESS WHERE COD_SESS = :SessionId";

            SessionData sessionData = null;

            using (OracleDataBase db = new OracleDataBase())
            {
                using (DbCommand cmd = db.GetSqlStringCommand(query))
                {
                    db.AddInParameter(cmd, "SessionId", DbType.Int32, sessionId);

                    using (IDataReader reader = db.ExecuteReader(cmd))
                    {
                        if (reader.Read())
                        {
                            sessionData = new SessionData();

                            sessionData.Id = sessionId;
                            sessionData.UserId = reader.GetValue<Int32>("CD_USUARIO");
                            sessionData.CompanyId = reader.GetValue<Int32>("COD_EMP");
                            sessionData.ExpiredDate = reader.GetValue<DateTime>("DATA_EXP");
                            sessionData.HostName = reader.GetValue<string>("NOME_HOST_USUA");
                        }
                    }
                }
            }
            return sessionData;
        }

        /// <summary>
        /// Renova a sessão, adequando a Data de Expiração
        /// </summary>
        /// <param name="sessionId"></param>
        public bool Renew(int sessionId)
        {
            return this.Update(sessionId, RetrieveExpiredDate());
        }

        /// <summary>
        /// Remove a sessão do usuário/empresa.
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="companyId"></param>
        public void RemoveByUserId(int userId, int companyId)
        {
            RemoveSession(userId, companyId);
        }

        /// <summary>
        /// Remove a sessão do usuário.
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="companyId"></param>
        private void RemoveSession(int userId, int companyId)
        {
            string query = @"DELETE FROM TFWSESS WHERE CD_USUARIO = :UserId";
            SessionData sessionData = new SessionData();

            using (OracleDataBase db = new OracleDataBase())
            {
                using (DbCommand cmd = db.GetSqlStringCommand(query))
                {

                    db.AddInParameter(cmd, "UserId", DbType.Int32, userId);

                    if (companyId != 0)
                    {
                        query += " AND COD_EMP = :CompanyId";
                        cmd.CommandText = query;
                        db.AddInParameter(cmd, "CompanyId", DbType.Int32, companyId);
                    }

                    db.ExecuteNonQuery(cmd);
                }
            }
        }

        /// <summary>
        /// Define o nome do HostName para uma determinada sessão.
        /// </summary>
        /// <param name="sessionId">chave de sessão</param>
        /// <param name="hostName">Nome ou IP da maquina</param>
        public void SetHostName(Int32 sessionId, string hostName)
        {
            string query = @"UPDATE TFWSESS SET NOME_HOST_USUA = :HostName WHERE COD_SESS = :SessionId";

            using (OracleDataBase db = new OracleDataBase())
            {
                try
                {
                    using (DbCommand cmd = db.GetSqlStringCommand(query))
                    {
                        db.AddInParameter(cmd, "SessionId", DbType.Int32, sessionId);
                        db.AddInParameter(cmd, "HostName", DbType.String, hostName);
                        db.ExecuteNonQuery(cmd);
                    }
                }
                catch (Exception ex)
                {
                    //TODO: Adicionar tratamento de logging
                    throw new InvalidSessionException("Error setting host name", ex);
                }
            }
        }
    }
}

